import { Body, Controller, Get, Param, ParseIntPipe, Post, Put, Query, Req } from '@nestjs/common';
import { AddDto, EditDto, PageDto } from './dto';
import { Tenant } from '@app/db/common/entities/Tenant';
import { TenantResult } from '../../types/result.type';
import { FindOptionsWhere, In, Like } from 'typeorm';
import { BaseController } from '../../base/base.controller';
import { DbService } from '@app/db';
import { DtoPipe } from '../../pipes/dto/dto.pipe';
import { createHash } from 'node:crypto'
import { CacheService } from '@app/cache';
import { Member } from '@app/db/common/entities/Member';
import { ConfigureService } from '@app/configure';
import { AuthService } from '../auth/auth.service';

@Controller('tenant')
export class TenantController extends BaseController{
    constructor(
        private readonly db:DbService,
        private readonly cache:CacheService,
        private readonly configure:ConfigureService,
        private readonly auth:AuthService
    ){
        super()
    }

    //获取子租户
    @Get('sub')
    async sub(@Query() querys:{id:string, name:string}, @Req() { belong, member }) {
        let where:FindOptionsWhere<Tenant> = {}

        if(belong == 0) {
            where.level = 0
        }else {
            const tenant = await this.db.tenant.findOne({id : member.tenantId}, {level : true})
            where.level = tenant.level + 1
        }


        if(querys.id) {
            let id = Number(querys.id)
            if(Number.isInteger(id) && id > 0) {
                where.id = id
            }
        }else if(querys.name) {
            where.name = Like('%' + querys.name + '%')
        }else {
            return this.failRes()
        }


        const tenants = await this.db.tenant.find(where, {name : true, id : true}, 0, 10)
        
        return this.successRes(tenants)
    }

    @Get('page')
    async page(@Query() dto:PageDto, @Req() { userId, isSuper }) {
        let result:PaginationResult<TenantResult & {profitRate?:string}> = {
            total : 0,
            rows : []
        }

        let where:FindOptionsWhere<Tenant> = {
            
        }


        if(dto.name) {
            where.name = Like('%' + dto.name + '%')
        }

        result.total = await this.db.tenant.count(where)

        if(result.total > 0) {
            result.rows = await this.db.tenant.pagination(dto.pageNo, dto.pageSize, where, null, {id : 'DESC'})

            if(result.rows.length > 0) {
                const ids:number[] = []
                const memberIds:number[] = []
                result.rows.forEach(row => {
                    memberIds.push(row.memberId)
                    ids.push(row.id)
                })

                
                const merchants = await this.db.merchant.find({id : In(ids)}, ['profitRate'])
                const members = await this.db.member.find({id : In(memberIds)}, ['name', 'account', 'id', 'roles'])

                result.rows.forEach(row => {
                    const i = members.findIndex(m => m.id = row.memberId)
                    if(i >= 0) {
                        row.admin = members[i]
                        members.splice(i, 1)
                    }

                    const ii = merchants.findIndex(m => m.id = row.id)
                    if(ii >= 0) {
                        row.profitRate = merchants[ii].profitRate
                        merchants.splice(ii, 1)
                    }
                })
            }
        }

        return this.successRes(result)
    }

    @Post('add')
    async add(@Body(new DtoPipe()) dto:AddDto, @Req() { locale, member, belong }) {
        
        if(true == await this.db.tenant.isExistsByWhere({name : dto.name})) {
            return this.failRes(this.i18n.t(locale, 'nameIsExists'))
        }

        const lockKey = createHash('sha256').update(dto.name).digest('hex')
        const lock = await this.cache.set(lockKey, '1', 10, 'NX')
        
        if(!lock) {
            return this.failRes(this.i18n.t(locale, 'nameIsExists'))
        }

        const time = this.utils.time()

        const tenant:KeyToValueType<Tenant> = {
            name : dto.name,
            creatorId : member.id,
            createTime : time
        }

        if(belong == 0) {
            tenant.level = 0
            tenant.parentId = 0;
        }else {
            tenant.parentId = member.tenantId
            const parent = await this.db.tenant.findOne({id : member.tenantId}, {level : true})
            tenant.level = parent.level + 1
        }

        if(dto.leader) {
            tenant.leader = dto.leader
        }

        if(dto.tel) {
            tenant.tel = dto.tel
        }

        if('enable' in dto) {
            tenant.enable = dto.enable
        }

        const memberData:KeyToValueType<Member> = {
            registerTime : time,
            creatorId : member.id,
            account : dto.account,
            enable : true,
            belong : 1,
            salt : this.auth.generateSalt()
        }

        memberData.password = this.auth.buildPassword(this.configure.get<string>('adminDefaultLoginPwd'), memberData.salt)

        if('roles' in dto) {
            memberData.roles = dto.roles
        }

        const queryRunner = this.db.tenant.createQueryRunner()
        await queryRunner.connect();
        await queryRunner.startTransaction();

        try {

            //插入租户
            const tenantRes = await queryRunner.manager.insert(Tenant, tenant)
            if(!tenantRes || !tenantRes.generatedMaps || tenantRes.generatedMaps.length == 0) {
                await queryRunner.rollbackTransaction()
                return this.failRes()
            }

            const tenantId = tenantRes.generatedMaps[0][this.db.tenant.primaryKey]

            memberData.tenantId = tenantId

            //插入用户
            const memberRes = await queryRunner.manager.insert(Member, memberData)
            if(!memberRes || !memberRes.generatedMaps || memberRes.generatedMaps.length == 0) {
                await queryRunner.rollbackTransaction()
                return this.failRes()
            }

            const res = await queryRunner.manager.update(Tenant, {id : tenantId}, {memberId : memberRes.generatedMaps[0].id})
            if(!res || res.affected == 0) {
                await queryRunner.rollbackTransaction()
                return this.failRes()
            }
            
            await queryRunner.commitTransaction()


            this.db.merchant.insert({id : tenantId, profitRate : String(dto.profitRate)})

            return this.successRes(memberRes.generatedMaps)

        } catch (error) {
            await queryRunner.rollbackTransaction()
        } finally {
            this.cache.del(lockKey)
            await queryRunner.release()
        }

       
    }

    @Put('edit/:id')
    async edit(@Body(new DtoPipe({skipNullProperties : true})) dto:EditDto, @Param('id', ParseIntPipe) id:number, @Req() { locale, userId }) {
        if(id <= 0) return

        const roles = 'roles' in dto ? dto.roles : null
        delete dto.roles

        const profitRate = dto.profitRate
        delete dto.profitRate

        

        let fields = Object.keys(dto) as (keyof Tenant)[]
        if(fields.length == 0) return

        if(roles) {
            fields.push('memberId')
        }

        const tenant = await this.db.tenant.findOne({ id }, fields)
        if(!tenant) return

        let updateData:KeyToValueType<Member> = {}
        for(let k in tenant) {
            if(k in dto && tenant[k] != dto[k]) {
                updateData[k] = dto[k]
            }
        }

        if(Object.keys(updateData).length == 0 && !roles) return this.successRes()

        if(updateData.name && (true == await this.db.tenant.isExistsByWhere({name : updateData.name}))) {
            return this.failRes(this.i18n.t(locale, 'nameExist'))
        }

        let lockKey:string;

        if(updateData.name) {
            lockKey = createHash('sha256').update(dto.name).digest('hex')
            const lock = await this.cache.set(lockKey, '1', 10, 'NX')
            
            if(!lock) {
                return this.failRes(this.i18n.t(locale, 'nameIsExists'))
            }
        }

        const queryRunner = this.db.tenant.createQueryRunner()
        await queryRunner.connect();
        await queryRunner.startTransaction();

        try {
            await queryRunner.manager.findOne(Tenant, {
                where : {id : id},
                select : ['id'],
                lock : {
                    mode : 'pessimistic_write'
                }
            })

            if(Object.keys(updateData).length > 0) {
                const res = await queryRunner.manager.update(Tenant, { id }, updateData)
                if(!res || res.affected == 0) {
                    await queryRunner.rollbackTransaction()
                    return this.failRes()
                }
            }

            if(roles) {
                const res = await queryRunner.manager.update(Member, { id : tenant.memberId }, { roles : roles})
                if(!res) {
                    await queryRunner.rollbackTransaction()
                    return this.failRes()
                }
            }

            if(profitRate !== undefined) {
                await this.db.merchant.update({id : id}, {profitRate : String(profitRate)})
            }

            await queryRunner.commitTransaction()

            return this.successRes()

        } catch (error) {
            await queryRunner.rollbackTransaction()
            return this.failRes()
        } finally {
            if(lockKey != undefined) this.cache.del(lockKey)
            await queryRunner.release()
        }
        
    }


    @Put('resetpwd')
    async resetpwd(@Body('id', ParseIntPipe) id:number) {
        const tenant = await this.db.tenant.findOne({id : id}, ['memberId'])
        if(!tenant) return
        const salt = this.auth.generateSalt()
        const password = this.auth.buildPassword(this.configure.get('adminDefaultLoginPwd'), salt)

        this.db.member.update({id : tenant.memberId}, {salt, password})
       
        return this.successRes()
    }
}
